﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Utils.App;
using System.Diagnostics;
using Utils.MathUtilitys;
using Utils.Misc;

namespace Utils.States
{
    public class State
    {
        /*
         * Common States
         */
        public static readonly StateId STATE_Init = "Init";
        public static readonly StateId STATE_Inactive = "Inactive";
        public static readonly StateId STATE_Dead = "Dead";
        public static readonly StateId STATE_Root = "Root";
        public static readonly StateId STATE_History = "History";
        public static readonly StateId STATE_DeepHistory = "DeepHistory";

        public delegate void ActivityMethodCtx(StateMachineCtx ctx);
        public delegate void ActivityMethod();

        /*
         * Fields
         */
        ActivityMethod entryMtd;
        ActivityMethodCtx entryMtdCtx;
        ActivityMethod exitMtd;
        ActivityMethodCtx exitMtdCtx;
        ActivityMethod execMtd;
        ActivityMethodCtx execMtdCtx;

        StateId id;
        RangeValue<float> entryTimer;
        Timer timer = new Timer(0);
        State history;
        int nodeDepth;
        bool active = false;

        State parent;
        List<State> children;
        List<Transition> transitions = new List<Transition>();

        public EventHandler<EventTransitionArgs> EventTransition;

        /*
         * Properties
         */
        public StateId ID { get { return id; } }
        public Timer Timer { get { return timer; } }
        public State History { get { return history; } set { history = value; } }
        public State Parent { get { return parent; } }
        public int NodeDepth { get { return nodeDepth; } }
        public bool Active { get { return active; } }

        /*
         * Constructor
         */
        public State()
        {
            id = STATE_Root;
        }

        public State(StateId id, State parent)
        {
            this.id = id;
            this.parent = parent;
            nodeDepth = parent.NodeDepth + 1;
            Debug.Assert(id != null, "Non initialised StateId used");
        }

        /*
         * Methods
         */
        public void Update(float dTime)
        {
            timer.Update(dTime);
        }

        public bool Timeout()
        {
            return timer.Timeout();
        }

        public State Add(StateId id)
        {
            State child = new State(id, this);
            child.EventTransition = EventTransition;

            if (children == null)
            {
                children = new List<State>();
            }
            children.Add(child);

            return child;
        }

        public State EntryMtd(ActivityMethod entryMtd)
        {
            this.entryMtd = entryMtd;
            return this;
        }

        public State EntryMtd(ActivityMethodCtx entryMtd)
        {
            this.entryMtdCtx = entryMtd;
            return this;
        }

        public State ExitMtd(ActivityMethod exitMtd)
        {
            this.exitMtd = exitMtd;
            return this;
        }

        public State ExitMtd(ActivityMethodCtx exitMtd)
        {
            this.exitMtdCtx = exitMtd;
            return this;
        }

        public State ExecMtd(ActivityMethod execMtd)
        {
            this.execMtd = execMtd;
            return this;
        }

        public State ExecMtd(ActivityMethodCtx execMtd)
        {
            this.execMtdCtx = execMtd;
            return this;
        }

        public State EntryTimer(float timer)
        {
            entryTimer = new RangeValue<float>(timer, timer);
            return this;
        }

        public State EntryTimer(RangeValue<float> timer)
        {
            entryTimer = timer;
            return this;
        }

        public Transition AddTransition(State to)
        {
            return AddTransition(to, null, null);
        }

        public Transition AddTransition(State to,  Transition.GCMethod guardCondition)
        {
            return AddTransition(to, guardCondition, null);
        }

        public Transition AddTransition(State to, Transition.GCMethodCtx guardCondition)
        {
            return AddTransition(to, null, guardCondition);
        }

        Transition AddTransition(
            State to, 
            Transition.GCMethod guardCondition, Transition.GCMethodCtx guardConditionCtx)
        {
            Transition t = new Transition(this, to, guardCondition, guardConditionCtx);
            transitions.Add(t);
            return t;
        }

        public Transition FindTransition(StateMachineCtx ctx)
        {
            foreach (Transition transition in transitions)
            {
                if (!transition.HasTrigger() && transition.CheckGuardConditions(ctx))
                {
                    return transition;
                }
            }

            return null;
        }

        public bool IsHistoryState()
        {
            return id == STATE_History || id == STATE_DeepHistory;
        }

        public State GetInitState()
        {
            if (children != null)
            {
                return children[0];
            }
            return null;
        }

        public State GetDeepHistory()
        {
            if (history != null)
            {
                if (history.History == null)
                {
                    return history;
                }
                else
                {
                    return history.GetDeepHistory();
                }
            }
            return null;
        }

        public void DoEnter(StateMachineCtx ctx)
        {
            if (entryTimer != null)
            {
                timer.Set(RndUtils.RndfInRange(entryTimer));
            }
            active = true;

            if (entryMtd != null)
            {
                entryMtd();
            }
            if (entryMtdCtx != null)
            {
                entryMtdCtx(ctx);
            }
        }

        public void DoExit(StateMachineCtx ctx)
        {
            active = false;

            if (exitMtd != null)
            {
                exitMtd();
            }
            if (exitMtdCtx != null)
            {
                exitMtdCtx(ctx);
            }
        }

        public void DoExec(StateMachineCtx ctx)
        {
            if (execMtd != null)
            {
                execMtd();
            }
            if (execMtdCtx != null)
            {
                execMtdCtx(ctx);
            }
        }

        public State GetStateById(StateId stateId)
        {
            if (this.id == stateId)
            {
                return this;
            }
            else if (children != null)
            {
                foreach (State child in children)
                {
                    State result = child.GetStateById(stateId);
                    if (result != null)
                    {
                        return result;
                    }
                }
            }
            return null;
        }
    }
}
